home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / chrome / toolkit.jar / content / global / nsDragAndDrop.js < prev    next >
Encoding:
JavaScript  |  2005-02-08  |  19.3 KB  |  552 lines

  1. //@line 39 "/c/mozilla/toolkit/content/nsDragAndDrop.js"
  2.  
  3. /** 
  4.  *  nsTransferable - a wrapper for nsITransferable that simplifies
  5.  *                   javascript clipboard and drag&drop. for use in
  6.  *                   these situations you should use the nsClipboard
  7.  *                   and nsDragAndDrop wrappers for more convenience
  8.  **/ 
  9.  
  10. var nsTransferable = {
  11.   /**
  12.    * nsITransferable set (TransferData aTransferData) ;
  13.    *
  14.    * Creates a transferable with data for a list of supported types ("flavours")
  15.    * 
  16.    * @param TransferData aTransferData
  17.    *        a javascript object in the format described above 
  18.    **/ 
  19.   set: function (aTransferDataSet)
  20.     {
  21.       var trans = this.createTransferable();
  22.       for (var i = 0; i < aTransferDataSet.dataList.length; ++i) 
  23.         {
  24.           var currData = aTransferDataSet.dataList[i];
  25.           var currFlavour = currData.flavour.contentType;
  26.           trans.addDataFlavor(currFlavour);
  27.           var supports = null; // nsISupports data
  28.           var length = 0;
  29.           if (currData.flavour.dataIIDKey == "nsISupportsString")
  30.             {
  31.               supports = Components.classes["@mozilla.org/supports-string;1"]
  32.                                    .createInstance(Components.interfaces.nsISupportsString);
  33.  
  34.               supports.data = currData.supports;
  35.               length = supports.data.length;
  36.             }
  37.           else 
  38.             {
  39.               // non-string data. TBD!
  40.             }
  41.           trans.setTransferData(currFlavour, supports, length * 2);
  42.         }
  43.       return trans;
  44.     },
  45.   
  46.   /**
  47.    * TransferData/TransferDataSet get (FlavourSet aFlavourSet, 
  48.    *                                   Function aRetrievalFunc, Boolean aAnyFlag) ;
  49.    *
  50.    * Retrieves data from the transferable provided in aRetrievalFunc, formatted
  51.    * for more convenient access.
  52.    *
  53.    * @param FlavourSet aFlavourSet
  54.    *        a FlavourSet object that contains a list of supported flavours.
  55.    * @param Function aRetrievalFunc
  56.    *        a reference to a function that returns a nsISupportsArray of nsITransferables
  57.    *        for each item from the specified source (clipboard/drag&drop etc)
  58.    * @param Boolean aAnyFlag
  59.    *        a flag specifying whether or not a specific flavour is requested. If false,
  60.    *        data of the type of the first flavour in the flavourlist parameter is returned,
  61.    *        otherwise the best flavour supported will be returned.
  62.    **/
  63.   get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
  64.     {
  65.       if (!aRetrievalFunc) 
  66.         throw "No data retrieval handler provided!";
  67.       
  68.       var supportsArray = aRetrievalFunc(aFlavourSet);
  69.       var dataArray = [];
  70.       var count = supportsArray.Count();
  71.       
  72.       // Iterate over the number of items returned from aRetrievalFunc. For
  73.       // clipboard operations, this is 1, for drag and drop (where multiple
  74.       // items may have been dragged) this could be >1.
  75.       for (var i = 0; i < count; i++)
  76.         {
  77.           var trans = supportsArray.GetElementAt(i);
  78.           if (!trans) continue;
  79.           trans = trans.QueryInterface(Components.interfaces.nsITransferable);
  80.             
  81.           var data = { };
  82.           var length = { };
  83.           
  84.           var currData = null;
  85.           if (aAnyFlag)
  86.             { 
  87.               var flavour = { };
  88.               trans.getAnyTransferData(flavour, data, length);
  89.               if (data && flavour)
  90.                 {
  91.                   var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
  92.                   if (selectedFlavour) 
  93.                     dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
  94.                 }
  95.             }
  96.           else
  97.             {
  98.               var firstFlavour = aFlavourSet.flavours[0];
  99.               trans.getTransferData(firstFlavour, data, length);
  100.               if (data && firstFlavour)
  101.                 dataArray[i] = FlavourToXfer(data.value, length.value, firstFlavour);
  102.             }
  103.         }
  104.       return new TransferDataSet(dataArray);
  105.     },
  106.  
  107.   /** 
  108.    * nsITransferable createTransferable (void) ;
  109.    *
  110.    * Creates and returns a transferable object.
  111.    **/    
  112.   createTransferable: function ()
  113.     {
  114.       const kXferableContractID = "@mozilla.org/widget/transferable;1";
  115.       const kXferableIID = Components.interfaces.nsITransferable;
  116.       return Components.classes[kXferableContractID].createInstance(kXferableIID);
  117.     }
  118. };  
  119.  
  120. /** 
  121.  * A FlavourSet is a simple type that represents a collection of Flavour objects.
  122.  * FlavourSet is constructed from an array of Flavours, and stores this list as
  123.  * an array and a hashtable. The rationale for the dual storage is as follows:
  124.  * 
  125.  * Array: Ordering is important when adding data flavours to a transferable. 
  126.  *        Flavours added first are deemed to be 'preferred' by the client. 
  127.  * Hash:  Convenient lookup of flavour data using the content type (MIME type)
  128.  *        of data as a key. 
  129.  */
  130. function FlavourSet(aFlavourList)
  131. {
  132.   this.flavours = aFlavourList || [];
  133.   this.flavourTable = { };
  134.  
  135.   this._XferID = "FlavourSet";
  136.   
  137.   for (var i = 0; i < this.flavours.length; ++i)
  138.     this.flavourTable[this.flavours[i].contentType] = this.flavours[i];
  139. }
  140.  
  141. FlavourSet.prototype = {
  142.   appendFlavour: function (aFlavour, aFlavourIIDKey)
  143.   {
  144.     var flavour = new Flavour (aFlavour, aFlavourIIDKey);
  145.     this.flavours.push(flavour);
  146.     this.flavourTable[flavour.contentType] = flavour;
  147.   }
  148. };
  149.  
  150. /** 
  151.  * A Flavour is a simple type that represents a data type that can be handled. 
  152.  * It takes a content type (MIME type) which is used when storing data on the
  153.  * system clipboard/drag and drop, and an IIDKey (string interface name
  154.  * which is used to QI data to an appropriate form. The default interface is
  155.  * assumed to be wide-string.
  156.  */ 
  157. function Flavour(aContentType, aDataIIDKey)
  158. {
  159.   this.contentType = aContentType;
  160.   this.dataIIDKey = aDataIIDKey || "nsISupportsString";
  161.  
  162.   this._XferID = "Flavour";
  163. }
  164.  
  165. function TransferDataBase() {}
  166. TransferDataBase.prototype = {
  167.   push: function (aItems)
  168.   {
  169.     this.dataList.push(aItems);
  170.   },
  171.  
  172.   get first ()
  173.   {
  174.     return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
  175.   }
  176. };
  177.  
  178. /** 
  179.  * TransferDataSet is a list (array) of TransferData objects, which represents
  180.  * data dragged from one or more elements. 
  181.  */
  182. function TransferDataSet(aTransferDataList)
  183. {
  184.   this.dataList = aTransferDataList || [];
  185.  
  186.   this._XferID = "TransferDataSet";
  187. }
  188. TransferDataSet.prototype = TransferDataBase.prototype;
  189.  
  190. /** 
  191.  * TransferData is a list (array) of FlavourData for all the applicable content
  192.  * types associated with a drag from a single item. 
  193.  */
  194. function TransferData(aFlavourDataList)
  195. {
  196.   this.dataList = aFlavourDataList || [];
  197.  
  198.   this._XferID = "TransferData";
  199. }
  200. TransferData.prototype = {
  201.   __proto__: TransferDataBase.prototype,
  202.   
  203.   addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
  204.   {
  205.     this.dataList.push(new FlavourData(aData, aLength, 
  206.                        new Flavour(aFlavourString, aDataIIDKey)));
  207.   }
  208. };
  209.  
  210. /** 
  211.  * FlavourData is a type that represents data retrieved from the system 
  212.  * clipboard or drag and drop. It is constructed internally by the Transferable
  213.  * using the raw (nsISupports) data from the clipboard, the length of the data,
  214.  * and an object of type Flavour representing the type. Clients implementing
  215.  * IDragDropObserver receive an object of this type in their implementation of
  216.  * onDrop. They access the 'data' property to retrieve data, which is either data 
  217.  * QI'ed to a usable form, or unicode string. 
  218.  */
  219. function FlavourData(aData, aLength, aFlavour) 
  220. {
  221.   this.supports = aData;
  222.   this.contentLength = aLength;
  223.   this.flavour = aFlavour || null;
  224.   
  225.   this._XferID = "FlavourData";
  226. }
  227.  
  228. FlavourData.prototype = {
  229.   get data ()
  230.   {
  231.     if (this.flavour && 
  232.         this.flavour.dataIIDKey != "nsISupportsString" )
  233.       return this.supports.QueryInterface(Components.interfaces[this.flavour.dataIIDKey]); 
  234.     else {
  235.       var unicode = this.supports.QueryInterface(Components.interfaces.nsISupportsString);
  236.       if (unicode) 
  237.         return unicode.data.substring(0, this.contentLength/2);
  238.      
  239.       return this.supports;
  240.     }
  241.     return "";
  242.   }
  243. }
  244.  
  245. /** 
  246.  * Create a TransferData object with a single FlavourData entry. Used when 
  247.  * unwrapping data of a specific flavour from the drag service. 
  248.  */
  249. function FlavourToXfer(aData, aLength, aFlavour) 
  250. {
  251.   return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
  252. }
  253.  
  254. var transferUtils = {
  255.  
  256.   retrieveURLFromData: function (aData, flavour)
  257.   {
  258.     switch (flavour) {
  259.       case "text/unicode":
  260.         return aData;
  261.       case "text/x-moz-url":
  262.         return aData.toString().split("\n")[0];
  263.       case "application/x-moz-file":
  264.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  265.                                   .getService(Components.interfaces.nsIIOService);
  266.         var fileHandler = ioService.getProtocolHandler("file")
  267.                                    .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  268.         return fileHandler.getURLSpecFromFile(aData);
  269.     }
  270.     return null;                                                   
  271.   }
  272.  
  273. }
  274.  
  275. /**
  276.  * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
  277.  *                 and nsIDragService/nsIDragSession. 
  278.  *
  279.  * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
  280.  *   'ondragdrop' event handlers on your XML element, e.g.                   
  281.  *   <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"   
  282.  *               ondragover="nsDragAndDrop.startDrag(event, observer);"      
  283.  *               ondragdrop="nsDragAndDrop.drop(event, observer);"/>         
  284.  *                                                                           
  285.  *   You need to create an observer js object with the following member      
  286.  *   functions:                                                              
  287.  *     Object onDragStart (event)        // called when drag initiated,      
  288.  *                                       // returns flavour list with data   
  289.  *                                       // to stuff into transferable      
  290.  *     void onDragOver (Object flavour)  // called when element is dragged   
  291.  *                                       // over, so that it can perform     
  292.  *                                       // any drag-over feedback for provided
  293.  *                                       // flavour                          
  294.  *     void onDrop (Object data)         // formatted data object dropped.   
  295.  *     Object getSupportedFlavours ()    // returns a flavour list so that   
  296.  *                                       // nsTransferable can determine
  297.  *                                       // whether or not to accept drop. 
  298.  **/   
  299.  
  300. var nsDragAndDrop = {
  301.   
  302.   _mDS: null,
  303.   get mDragService()
  304.     {
  305.       if (!this._mDS) 
  306.         {
  307.           const kDSContractID = "@mozilla.org/widget/dragservice;1";
  308.           const kDSIID = Components.interfaces.nsIDragService;
  309.           this._mDS = Components.classes[kDSContractID].getService(kDSIID);
  310.         }
  311.       return this._mDS;
  312.     },
  313.  
  314.   /**
  315.    * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
  316.    *
  317.    * called when a drag on an element is started.
  318.    *
  319.    * @param DOMEvent aEvent
  320.    *        the DOM event fired by the drag init
  321.    * @param Object aDragDropObserver
  322.    *        javascript object of format described above that specifies
  323.    *        the way in which the element responds to drag events.
  324.    **/  
  325.   startDrag: function (aEvent, aDragDropObserver)
  326.     {
  327.       if (!("onDragStart" in aDragDropObserver))
  328.         return;
  329.  
  330.       const kDSIID = Components.interfaces.nsIDragService;
  331.       var dragAction = { action: kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_MOVE + kDSIID.DRAGDROP_ACTION_LINK };
  332.  
  333.       var transferData = { data: null };
  334.       try 
  335.         {
  336.           aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
  337.         }
  338.       catch (e) 
  339.         {
  340.           return;  // not a draggable item, bail!
  341.         }
  342.  
  343.       if (!transferData.data) return;
  344.       transferData = transferData.data;
  345.       
  346.       var transArray = Components.classes["@mozilla.org/supports-array;1"]
  347.                                  .createInstance(Components.interfaces.nsISupportsArray);
  348.  
  349.       var region = null;
  350.       if (aEvent.originalTarget.localName == "treechildren") {
  351.         // let's build the drag region
  352.         var tree = aEvent.originalTarget.parentNode;
  353.         try {
  354.           region = Components.classes["@mozilla.org/gfx/region;1"].createInstance(Components.interfaces.nsIScriptableRegion);
  355.           region.init();
  356.           var obo = tree.treeBoxObject;
  357.           var bo = obo.treeBody.boxObject;
  358.           var sel= obo.view.selection;
  359.  
  360.           var rowX = bo.x;
  361.           var rowY = bo.y;
  362.           var rowHeight = obo.rowHeight;
  363.           var rowWidth = bo.width;
  364.  
  365.           //add a rectangle for each visible selected row
  366.           for (var i = obo.getFirstVisibleRow(); i <= obo.getLastVisibleRow(); i ++)
  367.           {
  368.             if (sel.isSelected(i))
  369.               region.unionRect(rowX, rowY, rowWidth, rowHeight);
  370.             rowY = rowY + rowHeight;
  371.           }
  372.       
  373.           //and finally, clip the result to be sure we don't spill over...
  374.           region.intersectRect(bo.x, bo.y, bo.width, bo.height);
  375.         } catch(ex) {
  376.           dump("Error while building selection region: " + ex + "\n");
  377.           region = null;
  378.         }
  379.       }
  380.  
  381.       var count = 0;
  382.       do 
  383.         {
  384.           var trans = nsTransferable.set(transferData._XferID == "TransferData" 
  385.                                          ? transferData 
  386.                                          : transferData.dataList[count++]);
  387.           transArray.AppendElement(trans.QueryInterface(Components.interfaces.nsISupports));
  388.         }
  389.       while (transferData._XferID == "TransferDataSet" && 
  390.              count < transferData.dataList.length);
  391.       
  392.       try {
  393.         this.mDragService.invokeDragSession(aEvent.target, transArray, region, dragAction.action);
  394.       }
  395.       catch(ex) {
  396.         // this could be because the user pressed escape to
  397.         // cancel the drag. even if it's not, there's not much
  398.         // we can do, so be silent.
  399.       }
  400.       aEvent.preventBubble();
  401.     },
  402.  
  403.   /** 
  404.    * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
  405.    *
  406.    * called when a drag passes over this element
  407.    *
  408.    * @param DOMEvent aEvent
  409.    *        the DOM event fired by passing over the element
  410.    * @param Object aDragDropObserver
  411.    *        javascript object of format described above that specifies
  412.    *        the way in which the element responds to drag events.
  413.    **/
  414.   dragOver: function (aEvent, aDragDropObserver)
  415.     { 
  416.       if (!("onDragOver" in aDragDropObserver)) 
  417.         return;
  418.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  419.         return;
  420.       var flavourSet = aDragDropObserver.getSupportedFlavours();
  421.       for (var flavour in flavourSet.flavourTable)
  422.         {
  423.           if (this.mDragSession.isDataFlavorSupported(flavour))
  424.             {
  425.               aDragDropObserver.onDragOver(aEvent, 
  426.                                            flavourSet.flavourTable[flavour], 
  427.                                            this.mDragSession);
  428.               aEvent.preventBubble();
  429.               break;
  430.             }
  431.         }
  432.     },
  433.  
  434.   mDragSession: null,
  435.  
  436.   /** 
  437.    * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
  438.    *
  439.    * called when the user drops on the element
  440.    *
  441.    * @param DOMEvent aEvent
  442.    *        the DOM event fired by the drop
  443.    * @param Object aDragDropObserver
  444.    *        javascript object of format described above that specifies
  445.    *        the way in which the element responds to drag events.
  446.    **/
  447.   drop: function (aEvent, aDragDropObserver)
  448.     {
  449.       if (!("onDrop" in aDragDropObserver))
  450.         return;
  451.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  452.         return;  
  453.       if (this.mDragSession.canDrop) {
  454.         var flavourSet = aDragDropObserver.getSupportedFlavours();
  455.         var transferData = nsTransferable.get(flavourSet, this.getDragData, true);
  456.         // hand over to the client to respond to dropped data
  457.         var multiple = "canHandleMultipleItems" in aDragDropObserver && aDragDropObserver.canHandleMultipleItems;
  458.         var dropData = multiple ? transferData : transferData.first.first;
  459.         aDragDropObserver.onDrop(aEvent, dropData, this.mDragSession);
  460.       }
  461.       aEvent.preventBubble();
  462.     },
  463.  
  464.   /** 
  465.    * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
  466.    *
  467.    * called when a drag leaves this element
  468.    *
  469.    * @param DOMEvent aEvent
  470.    *        the DOM event fired by leaving the element
  471.    * @param Object aDragDropObserver
  472.    *        javascript object of format described above that specifies
  473.    *        the way in which the element responds to drag events.
  474.    **/
  475.   dragExit: function (aEvent, aDragDropObserver)
  476.     {
  477.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  478.         return;
  479.       if ("onDragExit" in aDragDropObserver)
  480.         aDragDropObserver.onDragExit(aEvent, this.mDragSession);
  481.     },  
  482.     
  483.   /** 
  484.    * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
  485.    *
  486.    * called when a drag enters in this element
  487.    *
  488.    * @param DOMEvent aEvent
  489.    *        the DOM event fired by entering in the element
  490.    * @param Object aDragDropObserver
  491.    *        javascript object of format described above that specifies
  492.    *        the way in which the element responds to drag events.
  493.    **/
  494.   dragEnter: function (aEvent, aDragDropObserver)
  495.     {
  496.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  497.         return;
  498.       if ("onDragEnter" in aDragDropObserver)
  499.         aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
  500.     },  
  501.     
  502.   /** 
  503.    * nsISupportsArray getDragData (Object aFlavourList)
  504.    *
  505.    * Creates a nsISupportsArray of all droppable items for the given
  506.    * set of supported flavours.
  507.    * 
  508.    * @param FlavourSet aFlavourSet
  509.    *        formatted flavour list.
  510.    **/  
  511.   getDragData: function (aFlavourSet)
  512.     {
  513.       var supportsArray = Components.classes["@mozilla.org/supports-array;1"]
  514.                                     .createInstance(Components.interfaces.nsISupportsArray);
  515.  
  516.       for (var i = 0; i < nsDragAndDrop.mDragSession.numDropItems; ++i)
  517.         {
  518.           var trans = nsTransferable.createTransferable();
  519.           for (var j = 0; j < aFlavourSet.flavours.length; ++j)
  520.             trans.addDataFlavor(aFlavourSet.flavours[j].contentType);
  521.           nsDragAndDrop.mDragSession.getData(trans, i);
  522.           supportsArray.AppendElement(trans);
  523.         }
  524.       return supportsArray;
  525.     },
  526.  
  527.   /** 
  528.    * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
  529.    *
  530.    * Sets the canDrop attribute for the drag session.
  531.    * returns false if there is no current drag session.
  532.    *
  533.    * @param DOMEvent aEvent
  534.    *        the DOM event fired by the drop
  535.    * @param Object aDragDropObserver
  536.    *        javascript object of format described above that specifies
  537.    *        the way in which the element responds to drag events.
  538.    **/
  539.   checkCanDrop: function (aEvent, aDragDropObserver)
  540.     {
  541.       if (!this.mDragSession) 
  542.         this.mDragSession = this.mDragService.getCurrentSession();
  543.       if (!this.mDragSession) 
  544.         return false;
  545.       this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
  546.       if ("canDrop" in aDragDropObserver)
  547.         this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
  548.       return true;
  549.     } 
  550. };
  551.  
  552.